﻿/*
   Copyright 2012 University of Southampton

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
*/

using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Threading;
using System.Diagnostics;
using uk.ac.soton.ses.DAL;

namespace uk.ac.soton.ses
{
    /// <summary>
    /// Example file system watcher. Performs certain operations depending on the contents of the watched directory
    /// </summary>
    public sealed class HDCFileSystemWatcher:IDisposable
    {
        private FileSystemWatcher _fsw = null;
        private string folderToWatch;
        private int basePathDepthInWatchFolder;
        private string trackingFileParent;
        private string[] fileNamesToIgnore;
        private HDCPlugins<IHDCMetadataSyncServicePlugin> _syncServicePlugins;

        //We will set a default level of logging to ensure all messages are reported until it is overridden by configuration settings in database (in constructor)
        private static TraceLevel loggingVerbosity = TraceLevel.Verbose;

        private Timer _processUpdatesTimer = null;
        private Timer _folderCheckTimer = null;

        private int scheduler_ping_time = 5000; //Every 5 seconds
        private int scheduler_check_timer = 60000; //Every minute
        //private int scheduler_check_timer = 300000; //Every 5 minutes
        //private int scheduler_check_timer = 3600000; //Every hour

        //Properties
        public string WatchFolder { get { return this.folderToWatch; } }

        public int BasePathDepthInWatchFolder { get { return this.basePathDepthInWatchFolder; } }

        public string TrackingFileParent { get { return this.trackingFileParent; } }

        public string[] FileNamesToIgnore { get { return this.fileNamesToIgnore; } }

        public HDCConfigurationOptionsFileSystemWatcher ConfigOptions { get; set; }

        [System.Security.Permissions.SecurityPermission(System.Security.Permissions.SecurityAction.LinkDemand)]
        public HDCFileSystemWatcher(HDCConfigurationOptionsFileSystemWatcher configOptions)
        {
            this.ConfigOptions = configOptions;
            this.folderToWatch = configOptions.LocalPath;
            this.basePathDepthInWatchFolder = configOptions.BasePathDepthInWatchFolder;
            this.trackingFileParent = configOptions.TrackingFolderName;
            this.fileNamesToIgnore = configOptions.FileNamesToIgnore;
            HDCFileSystemWatcher.loggingVerbosity= configOptions.LoggingVerbosity;

            //Check existing data, before we start the file system watcher and timer
            HDCWatcherQueueProcessing.QueueFolderChecksInWatchFolder(this);
            HDCWatcherQueueProcessing.QueueMetadataCheck(this);

            //Setup timers for scheduled tasks
            this._processUpdatesTimer = new Timer(new TimerCallback(this.ProcessWorkQueueTimerTask), null, 0, this.scheduler_ping_time);
            this._folderCheckTimer = new Timer(new TimerCallback(this.CheckFoldersTimerTask), null, this.scheduler_check_timer, this.scheduler_check_timer);

            //Setup the FileSystemWatcher
            this._fsw = new FileSystemWatcher();
            this._fsw.IncludeSubdirectories = true;
            this._fsw.Path = this.folderToWatch;
            this._fsw.Renamed += new RenamedEventHandler(ProcessEvent);
            this._fsw.Created += new FileSystemEventHandler(ProcessEvent);
            this._fsw.Deleted += new FileSystemEventHandler(ProcessEvent);
            this._fsw.Changed += new FileSystemEventHandler(ProcessEvent);
            this._fsw.EnableRaisingEvents = true;

            //Load plugins
            this._syncServicePlugins = new HDCPlugins<IHDCMetadataSyncServicePlugin>();
        }

        private void ProcessEvent(object sender, FileSystemEventArgs e)
        {
            this.PrintEventDebugInfo(e.FullPath);
            HDCWatcherQueueProcessing.QueueFolderCheck(e, this);
        }

        private void ProcessWorkQueueTimerTask(object o)
        {
            try
            {
                HDCWatcherQueueProcessing.ProcessWorkItemsInQueue();
            }
            //The database server has gone down so we will wait for it to come back up
            catch (System.Data.EntityCommandExecutionException e)
            {
                //Stop processing for a bit
                this._processUpdatesTimer.Change(System.Threading.Timeout.Infinite, System.Threading.Timeout.Infinite);

                HDCFileSystemWatcher.Message(string.Format("Exception {0}. Problem connecting to database. Stopped processing for five minutes.", e.Message), TraceLevel.Error);

                //Go to sleep for a bit
                Thread.Sleep(5 * 60 * 1000);

                //Resume processing
                this._processUpdatesTimer.Change(this.scheduler_ping_time, this.scheduler_ping_time);

                HDCFileSystemWatcher.Message("Resuming processing.", TraceLevel.Error);
            }
        }

        private void CheckFoldersTimerTask(object o)
        {
            HDCWatcherQueueProcessing.QueueFolderChecksInWatchFolder(this);
            HDCWatcherQueueProcessing.QueueMetadataCheck(this);
        }

        private void PrintEventDebugInfo(string path)
        {
            //Log what we have for debugging
            string message = string.Format("Event for path {0}", path);
            HDCFileSystemWatcher.Message(message, TraceLevel.Info);

            HDCPath hdcPath = new HDCPath(path, this.WatchFolder, this.BasePathDepthInWatchFolder);
            StringBuilder sb = new StringBuilder();
            sb.Append(string.Format(" ({0})", hdcPath.PathType));
            sb.Append(string.Format(", folderToWatch={0}", folderToWatch));
            sb.Append(string.Format(", folderToWatch.Length={0}", folderToWatch.Length));
            sb.Append(string.Format(", indexOfBasePathSeparator={0}", hdcPath.IndexOfBasePathSeparator));
            sb.Append(string.Format(", basePath={0}", hdcPath.BasePath));
            sb.Append(string.Format(", remainingPath={0}", hdcPath.RemainingPath));
            sb.Append(string.Format(", pathRelativeToWatchedFolder={0}", hdcPath.PathRelativeToWatchedFolder));
            sb.Append(string.Format(", objectName={0}", hdcPath.ObjectName));
            sb.Append(string.Format(", objectPath={0}", hdcPath.ObjectPath));
            sb.AppendLine(string.Format(", topLevelFolderName={0}", hdcPath.TopLevelFolderName));
            HDCFileSystemWatcher.Message(sb.ToString(), TraceLevel.Verbose);
        }

        [System.Security.Permissions.SecurityPermission(System.Security.Permissions.SecurityAction.LinkDemand)]
        public void Dispose()
        {
            using (AutoResetEvent processUpdatesWaitHandle = new AutoResetEvent(false))
            using (AutoResetEvent folderCheckWaitHandle = new AutoResetEvent(false))
            {
                //this.StopWatching();
                this._fsw.EnableRaisingEvents = false;
                this._fsw.Dispose();
                this._folderCheckTimer.Dispose(folderCheckWaitHandle);
                this._processUpdatesTimer.Dispose(processUpdatesWaitHandle);

                WaitHandle.WaitAll(new WaitHandle[] { processUpdatesWaitHandle, folderCheckWaitHandle }, 7500);
            }
        }

        public static void Message(string message, TraceLevel levelAtWhichToDisplay)
        {
            //Work out what colour to use
            ConsoleColor fgcolor = ConsoleColor.Gray;

            switch (levelAtWhichToDisplay)
            {
                case TraceLevel.Error:
                    fgcolor = ConsoleColor.Yellow;
                    break;
                case TraceLevel.Info:
                    fgcolor = ConsoleColor.White;
                    break;
                case TraceLevel.Verbose:
                    fgcolor = ConsoleColor.Gray;
                    break;
                case TraceLevel.Warning:
                    fgcolor = ConsoleColor.Cyan;
                    break;
            }

            //Display the message with the appropriate colour
            HDCFileSystemWatcher.Message(message, levelAtWhichToDisplay, fgcolor);
        }

        public List<IHDCMetadataSyncServicePlugin> GetPluginsForExtension(string extension)
        {
            return this._syncServicePlugins.Plugins.Where(plugin => plugin.Extension.Equals(extension, StringComparison.OrdinalIgnoreCase)).ToList();
        }

        public static void Message(string message, TraceLevel levelAtWhichToDisplay, ConsoleColor fgcolor)
        {
            if (HDCFileSystemWatcher.loggingVerbosity >= levelAtWhichToDisplay)
            {
                //We'll change the console colour for if there is a Console TraceListener, but this won't affect file listeners
                ConsoleColor oldfg = Console.ForegroundColor;
                Console.ForegroundColor = fgcolor;
                Trace.WriteLine(DateTime.Now.ToString("dd/MM/yyyy HH:mm:ss") + ": " + message);
                Console.ForegroundColor = oldfg;
            }
        }
    }
}
